{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "8acec6a0-2ac6-4c53-8e09-269b379bccf0",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🦥 Unsloth: Will patch your computer to enable 2x faster free finetuning.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/root/qwen_env/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🦥 Unsloth Zoo will now patch everything to make training faster!\n"
     ]
    }
   ],
   "source": [
    "from unsloth import FastLanguageModel\n",
    "from datasets import load_dataset\n",
    "from trl import SFTTrainer\n",
    "from transformers import TrainingArguments\n",
    "import torch\n",
    "from modelscope import snapshot_download"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "18d4494c-74c6-4b6c-a3ed-7d063aa4602a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 1. 下载模型（如果尚未下载）\n",
    "try:\n",
    "    model_dir = \"./Qwen/Qwen3-8B\"  # 如果已经下载过\n",
    "except:\n",
    "    model_dir = snapshot_download(\"Qwen/Qwen3-8B\", cache_dir=\"./\", revision=\"master\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "daebf938-f53a-4bdf-bff8-ade9dc958bcb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==((====))==  Unsloth 2025.6.2: Fast Qwen3 patching. Transformers: 4.51.3.\n",
      "   \\\\   /|    NVIDIA GeForce RTX 4090. Num GPUs = 1. Max memory: 23.65 GB. Platform: Linux.\n",
      "O^O/ \\_/ \\    Torch: 2.7.0+cu126. CUDA: 8.9. CUDA Toolkit: 12.6. Triton: 3.3.0\n",
      "\\        /    Bfloat16 = TRUE. FA [Xformers = 0.0.30. FA2 = False]\n",
      " \"-____-\"     Free license: http://github.com/unslothai/unsloth\n",
      "Unsloth: Fast downloading is enabled - ignore downloading bars which are red colored!\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Loading checkpoint shards: 100%|██████████| 5/5 [00:04<00:00,  1.14it/s]\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "model_dir = os.path.abspath(model_dir)  # 转换为绝对路径\n",
    "\n",
    "model, tokenizer = FastLanguageModel.from_pretrained(\n",
    "    model_name=model_dir,  # 现在使用绝对路径\n",
    "    max_seq_length=1024,\n",
    "    dtype=None,\n",
    "    load_in_4bit=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "cc604d68-0dc1-4b70-b14d-ec12b972d9a2",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Unsloth 2025.6.2 patched 36 layers with 36 QKV layers, 36 O layers and 36 MLP layers.\n"
     ]
    }
   ],
   "source": [
    "model = FastLanguageModel.get_peft_model(\n",
    "    model,\n",
    "    r=32,\n",
    "    target_modules=[\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\", \"gate_proj\", \"up_proj\", \"down_proj\"],\n",
    "    lora_alpha=64,\n",
    "    lora_dropout=0,\n",
    "    bias=\"none\",\n",
    "    use_gradient_checkpointing=\"unsloth\",\n",
    "    random_state=24,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "71803cd0-01d2-4c90-924e-016064d3c38f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据集结构示例: dict_keys(['messages', 'text'])\n",
      "第一个样本内容: {'messages': [{'content': '请从文本中抽取仇恨言论四元组，要求：\\n1. 严格按照以下格式回复：(评论对象 | 论点 | 目标群体 | 是否仇恨 [END])，直接输出，不要解释。\\n2. 如有多个四元组，两两之间用[SEP]分隔。\\n3. 目标群体可以包含以下6项中的一项或多项：Region、Racism、Sexism、LGBTQ、others、non-hate。注意仅当‘是否仇恨’项为‘non-hate时’，‘目标群体’项才为‘non-hate’。', 'role': 'system'}, {'content': '没爹的黑孩到处扔', 'role': 'user'}, {'content': '没爹的黑孩 | 到处扔 | Racism | hate [END]', 'role': 'assistant'}], 'text': '<|im_start|>system\\n请从文本中抽取仇恨言论四元组，要求：\\n1. 严格按照以下格式回复：(评论对象 | 论点 | 目标群体 | 是否仇恨 [END])，直接输出，不要解释。\\n2. 如有多个四元组，两两之间用[SEP]分隔。\\n3. 目标群体可以包含以下6项中的一项或多项：Region、Racism、Sexism、LGBTQ、others、non-hate。注意仅当‘是否仇恨’项为‘non-hate时’，‘目标群体’项才为‘non-hate’。<|im_end|>\\n<|im_start|>user\\n没爹的黑孩到处扔<|im_end|>\\n<|im_start|>assistant\\n<think>\\n\\n</think>\\n\\n没爹的黑孩 | 到处扔 | Racism | hate [END]<|im_end|>\\n'}\n"
     ]
    }
   ],
   "source": [
    "# 加载后立即检查第一个样本\n",
    "print(\"数据集结构示例:\", dataset[0].keys())\n",
    "print(\"第一个样本内容:\", dataset[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "6f391278-7fcb-4d9c-b6f1-5b118663ff70",
   "metadata": {},
   "outputs": [],
   "source": [
    "from datasets import load_dataset\n",
    "\n",
    "# 3. 加载并格式化数据集\n",
    "dataset = load_dataset(\"json\", data_files=\"train_huggingface_format.json\", split=\"train\")\n",
    "\n",
    "def apply_template(examples):\n",
    "    formatted_texts = [\n",
    "        tokenizer.apply_chat_template(\n",
    "            conv, \n",
    "            tokenize=False, \n",
    "            add_generation_prompt=False\n",
    "        ) \n",
    "        for conv in examples['messages']  # 关键修改：将'conversations'改为'messages'\n",
    "    ]\n",
    "    return {'text': formatted_texts}\n",
    "\n",
    "dataset = dataset.map(apply_template, batched=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "fd7083cd-1e70-47de-b85c-8ff60924ed75",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "==((====))==  Unsloth - 2x faster free finetuning | Num GPUs used = 1\n",
      "   \\\\   /|    Num examples = 4,000 | Num Epochs = 2 | Total steps = 500\n",
      "O^O/ \\_/ \\    Batch size per device = 4 | Gradient accumulation steps = 4\n",
      "\\        /    Data Parallel GPUs = 1 | Total batch size (4 x 4 x 1) = 16\n",
      " \"-____-\"     Trainable parameters = 87,293,952/8,278,029,312 (1.05% trained)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: Tracking run with swanlab version 0.6.3                                   \n",
      "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: Run data will be saved locally in \u001b[35m\u001b[1m/root/autodl-tmp/swanlog/run-20250614_141737-45da561d\u001b[0m\u001b[0m\n",
      "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: 👋 Hi \u001b[1m\u001b[39m1123whaad\u001b[0m\u001b[0m, welcome to swanlab!\n",
      "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: Syncing run \u001b[33moutputs\u001b[0m to the cloud\n",
      "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: 🏠 View project at \u001b[34m\u001b[4mhttps://swanlab.cn/@1123whaad/autodl-tmp\u001b[0m\u001b[0m\n",
      "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: 🚀 View run at \u001b[34m\u001b[4mhttps://swanlab.cn/@1123whaad/autodl-tmp/runs/ws65w17x0710nkg33ah7n\u001b[0m\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "<!DOCTYPE html>\n",
       "<html lang=\"en\">\n",
       "<head>\n",
       "    <meta charset=\"UTF-8\">\n",
       "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n",
       "    <title>Show Iframe</title>\n",
       "    \n",
       "        <script>\n",
       "            function showIframe() {\n",
       "                var iframeHtml = '<iframe src=\"https://swanlab.cn/@1123whaad/autodl-tmp/runs/ws65w17x0710nkg33ah7n\" width=100% height=\"600\" frameborder=\"no\"></iframe>';\n",
       "                document.getElementById('iframeContainer').innerHTML = iframeHtml;\n",
       "            }\n",
       "        </script>\n",
       "        \n",
       "</head>\n",
       "<body>\n",
       "    <style>\n",
       "        .interactive-button {\n",
       "            display: flex;\n",
       "            align-items: center;\n",
       "            height: 36px;\n",
       "            border: 0px;\n",
       "            background-color: #2c8f63;\n",
       "            color: white;\n",
       "            padding: 10px 20px;\n",
       "            transition: background-color 0.3s, transform 0.2s;\n",
       "        }\n",
       "\n",
       "        .interactive-button:hover {\n",
       "            background-color: #5cab87;\n",
       "            cursor: pointer;\n",
       "        }\n",
       "\n",
       "        .interactive-button:active { background-color: #217952; transform: scale(0.96); } </style> <br> <button \n",
       "        onclick=\"showIframe()\" class=\"interactive-button\"> <svg style=\"height: 16px; margin-right: 8px;\" viewBox=\"0 0 \n",
       "        46 46\" fill=\"none\"> <path d=\"M10.8439 21.1974C10.6414 21.2854 10.4477 21.3925 10.2655 21.5173L10.2069 \n",
       "        21.5652C10.1839 21.58 10.1625 21.5969 10.1429 21.6159C6.29135 24.6118 4.22831 29.4416 5.32646 34.282C5.94656 \n",
       "        37.0577 7.50461 39.5348 9.73801 41.2958C11.9714 43.0568 14.7436 43.994 17.5874 43.9495H18.0219C19.8864 \n",
       "        43.8697 21.7087 43.3694 23.3526 42.486C24.9964 41.6026 26.4193 40.3589 27.5147 38.848C28.61 37.3371 29.3496 \n",
       "        35.598 29.678 33.761C30.0065 31.9239 29.9153 30.0363 29.4112 28.2395C28.9181 26.4723 27.8919 24.8437 26.9937 \n",
       "        23.2551C25.4158 20.4653 23.8343 17.6764 22.2492 14.8884C21.7801 14.0647 21.3057 13.2465 20.8419 \n",
       "        12.4228C20.2315 11.3353 19.2746 10.1519 19.224 8.86183C19.1733 7.57176 20.2235 6.32701 21.5082 \n",
       "        6.07912C23.9284 5.61801 25.0639 8.24078 25.0693 8.23812C25.363 8.94035 25.9123 9.50489 26.6063 \n",
       "        9.81764C27.3002 10.1304 28.087 10.168 28.8077 9.92298C29.5283 9.67791 30.1291 9.1684 30.4885 8.49743C30.8479 \n",
       "        7.82646 30.9392 7.04405 30.7439 6.30835C30.1514 4.37314 28.9133 2.69953 27.2363 1.56656C25.7615 0.511704 \n",
       "        23.9847 -0.0372109 22.1719 0.00195984C20.9049 0.00893199 19.6532 0.27989 18.4967 0.797557C17.3402 1.31522 \n",
       "        16.3043 2.06823 15.4551 3.00856C14.49 4.08707 13.7984 5.38193 13.4389 6.78385C13.0794 8.18576 13.0624 9.6536 \n",
       "        13.3894 11.0635C13.52 11.593 13.6984 12.1095 13.9225 12.6067C14.5595 14.0514 15.4951 15.3681 16.284 \n",
       "        16.7355C17.2525 18.4147 18.2209 20.0948 19.1893 21.7758C20.1578 23.4568 21.1351 25.1449 22.1213 \n",
       "        26.8401C22.9209 28.2421 23.7925 29.4682 23.8805 31.1528C23.9175 32.0513 23.7682 32.9479 23.4419 \n",
       "        33.7859C23.1156 34.6239 22.6194 35.3854 21.9845 36.0223C21.3496 36.6592 20.5897 37.1578 19.7527 \n",
       "        37.4868C18.9157 37.8157 18.0196 37.9678 17.121 37.9336C14.0024 37.7923 11.6488 35.4814 11.1744 32.4588C10.58 \n",
       "        28.6419 13.552 26.5469 13.552 26.5469C14.1782 26.1785 14.6497 25.5955 14.8791 24.906C15.1084 24.2166 15.0801 \n",
       "        23.4673 14.7993 22.7971C14.5186 22.127 14.0044 21.5813 13.3521 21.2611C12.6998 20.941 11.9536 20.8682 11.2517 \n",
       "        21.0561C11.1174 21.0939 10.9856 21.1402 10.8572 21.1947\" fill=\"white\" /> <path d=\"M42.8101 31.5968C42.8109 \n",
       "        30.5198 42.7218 29.4445 42.5435 28.3823C42.2663 26.7069 41.7464 25.0808 41.0002 23.5552C40.5524 22.6463 \n",
       "        39.9874 21.7374 39.1024 21.2417C38.6593 20.9919 38.1589 20.8617 37.6502 20.8639C37.1416 20.8661 36.6423 \n",
       "        21.0006 36.2013 21.2541C35.7604 21.5077 35.393 21.8716 35.1352 22.3101C34.8775 22.7485 34.7382 23.2466 \n",
       "        34.7312 23.7552C34.7072 24.8773 35.3149 25.8875 35.768 26.9217C36.5212 28.6453 36.8623 30.5208 36.7642 \n",
       "        32.3993C36.6661 34.2777 36.1315 36.1075 35.2029 37.7433C35.146 37.8404 35.0952 37.941 35.051 38.0445C34.8623 \n",
       "        38.4842 34.7635 38.9573 34.7605 39.4358C34.7802 40.1222 35.0356 40.7808 35.4835 41.3011C35.9315 41.8214 \n",
       "        36.5449 42.1717 37.2207 42.2932C38.8759 42.589 40.1899 41.347 40.8856 39.9609C42.1643 37.3589 42.823 34.4961 \n",
       "        42.8101 31.5968Z\" fill=\"white\" /> <path d=\"M28.2309 11.8938C28.1761 11.9043 28.1218 11.9176 28.0683 \n",
       "        11.9338C27.9593 11.9642 27.8611 12.0249 27.7851 12.1088C27.7091 12.1928 27.6584 12.2965 27.6389 \n",
       "        12.408C27.6193 12.5195 27.6318 12.6343 27.6748 12.7391C27.7178 12.8438 27.7895 12.9343 27.8818 \n",
       "        12.9999C29.2375 14.0252 30.3809 15.3043 31.2482 16.7662C31.4838 17.1677 31.6888 17.5865 31.8612 \n",
       "        18.0189C32.0052 18.3921 32.1971 18.8799 32.6822 18.8532C33.0607 18.8346 33.2153 18.512 33.3192 \n",
       "        18.1895C33.8137 16.5125 33.9678 14.7534 33.7723 13.0159C33.6331 12.0693 33.4155 11.1359 33.122 \n",
       "        10.2252C33.0775 10.0047 32.9744 9.80029 32.8235 9.6335C32.7273 9.54627 32.6054 9.49262 32.4761 9.4806C32.3468 \n",
       "        9.46859 32.2171 9.49886 32.1065 9.56687C32.0016 9.65188 31.9115 9.75365 31.8399 9.86806C31.3956 10.4658 \n",
       "        30.825 10.9581 30.1687 11.3101C29.8377 11.4861 29.4893 11.6272 29.1292 11.7312C28.828 11.8192 28.5215 11.8325 \n",
       "        28.2309 11.8938Z\" fill=\"white\" /> </svg> Display SwanLab Board </button> <br> <div \n",
       "        id=\"iframeContainer\"></div> </body> </html>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Unsloth: Will smartly offload gradients to save VRAM!\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "    <div>\n",
       "      \n",
       "      <progress value='500' max='500' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
       "      [500/500 12:55, Epoch 2/2]\n",
       "    </div>\n",
       "    <table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       " <tr style=\"text-align: left;\">\n",
       "      <th>Step</th>\n",
       "      <th>Training Loss</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>10</td>\n",
       "      <td>3.580400</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>20</td>\n",
       "      <td>2.737100</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>30</td>\n",
       "      <td>1.457100</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>40</td>\n",
       "      <td>0.678100</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>50</td>\n",
       "      <td>0.620000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>60</td>\n",
       "      <td>0.624200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>70</td>\n",
       "      <td>0.604500</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>80</td>\n",
       "      <td>0.587300</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>90</td>\n",
       "      <td>0.570200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>100</td>\n",
       "      <td>0.635000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>110</td>\n",
       "      <td>0.598000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>120</td>\n",
       "      <td>0.654100</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>130</td>\n",
       "      <td>0.590400</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>140</td>\n",
       "      <td>0.603700</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>150</td>\n",
       "      <td>0.597700</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>160</td>\n",
       "      <td>0.626100</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>170</td>\n",
       "      <td>0.587500</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>180</td>\n",
       "      <td>0.557200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>190</td>\n",
       "      <td>0.580100</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>200</td>\n",
       "      <td>0.560000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>210</td>\n",
       "      <td>0.637000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>220</td>\n",
       "      <td>0.520800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>230</td>\n",
       "      <td>0.539300</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>240</td>\n",
       "      <td>0.586500</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>250</td>\n",
       "      <td>0.548600</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>260</td>\n",
       "      <td>0.540800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>270</td>\n",
       "      <td>0.576800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>280</td>\n",
       "      <td>0.589800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>290</td>\n",
       "      <td>0.513700</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>300</td>\n",
       "      <td>0.515900</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>310</td>\n",
       "      <td>0.494700</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>320</td>\n",
       "      <td>0.552000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>330</td>\n",
       "      <td>0.558800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>340</td>\n",
       "      <td>0.535000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>350</td>\n",
       "      <td>0.557800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>360</td>\n",
       "      <td>0.496600</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>370</td>\n",
       "      <td>0.570300</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>380</td>\n",
       "      <td>0.546100</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>390</td>\n",
       "      <td>0.596200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>400</td>\n",
       "      <td>0.556100</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>410</td>\n",
       "      <td>0.565000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>420</td>\n",
       "      <td>0.493600</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>430</td>\n",
       "      <td>0.523800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>440</td>\n",
       "      <td>0.522800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>450</td>\n",
       "      <td>0.555900</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>460</td>\n",
       "      <td>0.594300</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>470</td>\n",
       "      <td>0.472200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>480</td>\n",
       "      <td>0.551200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>490</td>\n",
       "      <td>0.565300</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>500</td>\n",
       "      <td>0.495600</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table><p>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "('lora_model/tokenizer_config.json',\n",
       " 'lora_model/special_tokens_map.json',\n",
       " 'lora_model/vocab.json',\n",
       " 'lora_model/merges.txt',\n",
       " 'lora_model/added_tokens.json',\n",
       " 'lora_model/tokenizer.json')"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 4. 训练配置\n",
    "trainer = SFTTrainer(\n",
    "    model=model,\n",
    "    tokenizer=tokenizer,\n",
    "    train_dataset=dataset,\n",
    "    dataset_text_field=\"text\",\n",
    "    max_seq_length=1024,\n",
    "    args=TrainingArguments(\n",
    "        per_device_train_batch_size=4,\n",
    "        gradient_accumulation_steps=4,\n",
    "        warmup_ratio=0.1,\n",
    "        num_train_epochs=2,\n",
    "        learning_rate=1e-4,\n",
    "        fp16=not torch.cuda.is_bf16_supported(),\n",
    "        bf16=torch.cuda.is_bf16_supported(),\n",
    "        logging_steps=10,\n",
    "        optim=\"adamw_8bit\",\n",
    "        weight_decay=0.01,\n",
    "        lr_scheduler_type=\"cosine\",\n",
    "        seed=3407,\n",
    "        output_dir=\"outputs\",\n",
    "        report_to=\"swanlab\",\n",
    "    ),\n",
    ")\n",
    "\n",
    "# 5. 开始训练\n",
    "trainer.train()\n",
    "\n",
    "# 6. 保存模型\n",
    "model.save_pretrained(\"lora_model\")\n",
    "tokenizer.save_pretrained(\"lora_model\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "bdc3add5-db48-4e71-8a22-a3948579ada4",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "正在处理样本 (Processing samples): 100%|██████████| 2000/2000 [33:09<00:00,  1.01it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "评估完成，结果已保存到 'demo1.txt'\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "import re\n",
    "from tqdm import tqdm\n",
    "import torch # 假设torch已被导入\n",
    "# from unsloth import FastLanguageModel # 假设FastLanguageModel已导入\n",
    "\n",
    "# 假设模型和分词器已经加载并准备好进行推理\n",
    "# model, tokenizer = ...\n",
    "FastLanguageModel.for_inference(model)\n",
    "\n",
    "# 1. 将固定的指令定义为变量，方便复用和修改\n",
    "instruction_text = (\n",
    "    \"请从文本中抽取仇恨言论四元组，要求：\\n\"\n",
    "    \"1. 严格按照以下格式回复：(评论对象 | 论点 | 目标群体 | 是否仇恨 [END])，直接输出，不要解释。\\n\"\n",
    "    \"2. 如有多个四元组，两两之间用[SEP]分隔。\\n\"\n",
    "    \"3. 目标群体选项：Region、Racism、Sexism、LGBTQ、others、non-hate。注意仅当‘是否仇恨’项为‘non-hate时’，‘目标群体’项才为‘non-hate’。\\n\"\n",
    ")\n",
    "\n",
    "# 建议为新格式的输出使用新文件名，以避免覆盖旧结果\n",
    "output_file_name = \"demo1.txt\" \n",
    "json_file_name = \"test1.json\" # 你的测试数据文件\n",
    "\n",
    "try:\n",
    "    with open(json_file_name, 'r', encoding='utf-8') as f:\n",
    "        data = json.load(f)\n",
    "except FileNotFoundError:\n",
    "    print(f\"错误: 文件 '{json_file_name}' 未找到。\")\n",
    "    exit()\n",
    "except json.JSONDecodeError:\n",
    "    print(f\"错误: 无法解析 '{json_file_name}' 中的JSON。请检查文件格式。\")\n",
    "    exit()\n",
    "\n",
    "with open(output_file_name, 'w', encoding='utf-8') as outfile:\n",
    "    # 使用tqdm显示进度条\n",
    "    for entry in tqdm(data, desc=\"正在处理样本 (Processing samples)\"):\n",
    "        content_text = entry.get(\"content\", \"\")\n",
    "        if not content_text:\n",
    "            continue\n",
    "\n",
    "        # 2. 构建符合ChatML格式的对话列表\n",
    "        messages = [\n",
    "            {\"role\": \"system\", \"content\": instruction_text},\n",
    "            {\"role\": \"user\", \"content\": content_text},\n",
    "            # 注意：这里没有 'assistant' 角色，因为这是我们要模型生成的部分\n",
    "        ]\n",
    "        \n",
    "        # 3. 使用tokenizer.apply_chat_template来创建推理提示\n",
    "        # - tokenize=True: 直接返回分词后的input_ids等。\n",
    "        # - add_generation_prompt=True: 在末尾自动添加 'assistant' 角色和分隔符，提示模型开始生成。\n",
    "        # - return_tensors=\"pt\": 返回PyTorch张量。\n",
    "        inputs = tokenizer.apply_chat_template(\n",
    "            messages,\n",
    "            tokenize=True,\n",
    "            add_generation_prompt=True,\n",
    "            return_tensors=\"pt\"\n",
    "        ).to(\"cuda\")\n",
    "\n",
    "        # 4. 模型生成\n",
    "        # - 添加 eos_token_id 参数是关键，它告诉模型在生成 <|im_end|> 后立即停止。\n",
    "        outputs = model.generate(\n",
    "            inputs,\n",
    "            max_new_tokens=128, # 根据需要调整\n",
    "            eos_token_id=tokenizer.eos_token_id\n",
    "        )\n",
    "        \n",
    "        # 5. 精确解码：只解码生成的部分，跳过作为输入的token\n",
    "        # 这是比正则表达式更稳健的方法。\n",
    "        input_length = inputs.shape[1]\n",
    "        generated_tokens = outputs[:, input_length:]\n",
    "        extracted_response = tokenizer.decode(generated_tokens[0], skip_special_tokens=True)\n",
    "\n",
    "        # 6. 将提取的响应写入文件\n",
    "        outfile.write(extracted_response.strip() + \"\\n\")\n",
    "\n",
    "print(f\"\\n评估完成，结果已保存到 '{output_file_name}'\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "567f0f5e-4081-4638-a34d-a288434b35ab",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "提取完成，结果已保存到 final.txt\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "\n",
    "# 输入文件和输出文件的路径\n",
    "input_file_path = 'demo1.txt'\n",
    "output_file_path = 'final.txt'\n",
    "\n",
    "# 打开输入文件并读取内容\n",
    "with open(input_file_path, 'r', encoding='utf-8') as file:\n",
    "    content = file.read()\n",
    "\n",
    "# 定义正则表达式匹配指定格式的内容\n",
    "# 匹配 <think>...</think> 后的任意内容，直到遇到下一个 <think> 或文件结尾\n",
    "pattern = r'<think>\\s*</think>\\s*(.*?)(?=<think>|\\Z)'\n",
    "\n",
    "# 使用正则表达式查找所有匹配的内容\n",
    "matches = re.findall(pattern, content, re.DOTALL)\n",
    "\n",
    "# 打开输出文件并写入匹配的内容\n",
    "with open(output_file_path, 'w', encoding='utf-8') as file:\n",
    "    for match in matches:\n",
    "        file.write(match.strip() + '\\n')\n",
    "\n",
    "print(f\"提取完成，结果已保存到 {output_file_path}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "abfafb5b-3503-4386-8a4f-7f245d53f127",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python (qwen_env)",
   "language": "python",
   "name": "qwen_kernel"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
